/*
 * Strength.java
 * 
 * last update: 16.01.2010 by Stefan Saru
 * 
 * author:	Alec(panovici@elcom.pub.ro)
 * 
 * Obs:
 */


package engine;

/**
 * Constants & functions for strength manipulation.
 * Strengths will be represented packed as a byte value, as follows:
 *<PRE>
 *      7                                0
 *      +-+--------------+-+--------------+
 *      |0|  Strength0   |0|  Strength1   |          this is for an ambiguous signal (has both 0 & 1 strength) if the value is X,
 *      +-+--------------+-+--------------+           and for an unambiguous strength signal if value is other than X
 * or:
 *      7                                0      \
 *      +-+--------------+-+--------------+      |
 *      |1| MinStrength1 |0| MaxStrength1 |      |   these formats are for an unambiguous value 
 *      +-+--------------+-+--------------+      |   signal with ambiguous strength
 *                                               |   (the first one for a 1 value , the second for 0)
 * or:                                           |   if MinStrength == MaxStrength => a simple unambiguous signal
 *      7                                0       |
 *      +-+--------------+-+--------------+      |
 *      |0| MinStrength0 |1| MaxStrength0 |      |
 *      +-+--------------+-+--------------+     /
 *     
 *</PRE>
 * A byte with value 0xff represents an invalid strength;
 */

class Strength {

	/**
	 * Use this to mark that a part of strngth has no relevance
	 */
	public static final byte nothing  = 0x8;  

	public static final byte supply   = 0x7;
	public static final byte strong   = 0x6;
	public static final byte pull     = 0x5;
	public static final byte large    = 0x4;
	public static final byte weak     = 0x3;
	public static final byte medium   = 0x2;
	public static final byte small    = 0x1;
	public static final byte highz    = 0x0;

	/**
	 * Strength reduction values for r?mos
	 * and r?tran
	 */
	static final byte reduceTable1[] =
	{
		highz,  //highs
		small,  //small
		small,  //medium
		medium, //weak
		medium, //large
		weak,   //pull
		pull,   //strong
		pull    //supply
	};

	/**
	 * Strength reduction values for ?mos
	 * and ?tran
	 */
	static final byte reduceTable2[] =
	{
		highz,  //highs
		small,  //small
		medium, //medium
		weak,   //weak
		large,  //large
		pull,   //pull
		strong, //strong
		strong  //supply
	};


	/**
	 * Default strengths for initial valus of nets. @see engine.Assignable for net types.
	 */
	public static final byte initStrengthDefaults[] =
	{getStrength(highz, highz),  //wire, tri
		getStrength(highz, highz),  //wand, triand
		getStrength(highz, highz),  //wor, trior
		getStrength(pull, pull),  //tri0
		getStrength(pull, pull),  //tri1
		getStrength(supply, supply),  //supply0
		getStrength(supply, supply),  //supply1
		getStrength(highz, highz)  //trireg: note that this is NOT
		//the value for the "not driven" state
	};


	public static final String strengthToString[] = {"highz",
		"small",
		"medium",
		"weak",
		"large",
		"pull",
		"strong",
		"supply",
	"nothing"};

	/**
	 * 'b10001000 masks the format bits
	 */
	public static final byte formatMask = (byte)0x88;

	static final int X = BitVector.X, Z = BitVector.Z;
	static final String bitToString[] = {"0", "1", "X", "Z"};

	public static final byte defaultStrength = getStrength(strong,
			strong);
	public static final byte defaultTriregStrength = getStrength(medium,
			medium);

	public static String strengthToString(byte strength, int bit) {
		String res;
		/*xConsole.debug("strength: " +
                   Integer.toHexString( 0x000000f & (int)strength) +
                   " bit: " + bit);*/
		return "(" + strengthToString[(strength >> 4) &  0x7] + ", " +
		strengthToString[strength & 0x7] + ")" + bitToString[bit];
	}

	public static byte getStrength(byte strength0, byte strength1) {
		return (byte)((strength0 << 4) | strength1);
	}
	/**
	 * Builds the strength byte according to strengths 0, 1 and sets
	 * the mask according to bit
	 */
	public static byte getStrength(byte strength0, byte strength1, int bit) {
		byte mask;
		switch(bit) {
		case 0:
			mask = 0x08;
			break;
		case 1:
			mask = (byte)0x80;
			break;
		default:
			mask = 0;
		}

		xConsole.debug("Strength.getStrength : " +
				(byte) ((strength0 << 4) | strength1 | mask));
		return (byte) ((strength0 << 4) | strength1 | mask);
	}

	public static byte getStrength1(byte strength) {
		return (byte) (strength & 0x7);
	}

	public static byte getStrength0(byte strength) {
		return (byte) ((strength >> 4) & 0x7);
	}

	/**
	 * @return the maximum strengh value for this strength
	 * when the signal is 0
	 */
	public static byte getMax0Strength(byte s) {
		int strength = ((int) s) & 0x000000ff;
		switch (strength & formatMask) {
		case 0:
			return (byte) ((strength >> 4) & 0x7);
		case 0x08:
			return (byte) (strength & 0x7);
		}

		return (byte) 0xff;
	}

	/**
	 * @return the maximum strengh value for this strength
	 * when the signal is 1
	 */
	public static byte getMax1Strength(byte s) {
		int strength = ((int) s) & 0x000000ff;
		switch (strength & formatMask) {
		case 0:
			return (byte) (strength & 0x7);
		case 0x80:
			return (byte) ((strength >> 4) & 0x7);
		}

		return (byte) 0xff;
	}

	/**
	 * reduces the strengths according to the
	 */
	public static byte reduce (byte strength, byte[] table) {
		byte oldMask = (byte) (strength & formatMask);
		return (byte) (oldMask |
				table[strength & 0x7] |
				(table[((((int) strength) & 0xff) >> 4) & 0x7] << 4));
	}

	/**
	 * @return the strength for the given value,
	 * or 0xff if the strength isn't defined
	 */
	public static byte getStrengthForValue(byte strength, int value) {
		if ((strength & formatMask) == 0) {
			switch(value) {
			case 1:
				if ((strength & formatMask) == 0)
					return getStrength1(strength);
			case 0:
				if ((strength & formatMask) == 0)
					return getStrength0(strength);
			case X:
				if (getStrength1(strength) == getStrength1(strength))
					return getStrength0(strength);
				return (byte)0xff;
			case Z: return 0;
			}
		}
		if (getStrength0(strength) == getStrength1(strength)) {
			//a range of strengths reduced to a single value
			if (value == 1 && (strength & formatMask) == 0x80)
				return getStrength0(strength); 
			if (value == 0 && ((strength & formatMask) == 0x08))
				return getStrength0(strength);
		}
		return (byte)0xff;    
	}

	/**
	 * An ambiguous strength is either the strength of an X signel or
	 * the strength of an unambiguous signel with a range of strengths
	 */
	public static boolean isAmbiguous(byte strength, int bit) {
		if ((strength & formatMask) == 0 && bit == X)return true;
		if ((strength & formatMask) != 0 &&
				(((strength >> 4) & 0x7) != (strength & 0x7)))return true;
		return false;
	}

	/**
	 * Does all the crap needed fot strength & value reconciliation ( :-( )
	 * Read it _ONLY_ if it's past 4A.M. and can't sleep.
	 */
	public static int reconcileDrivers(int truthTable[][],
			byte strength1, byte strength2,
			int bit1, int bit2)
	{
		byte strength;
		int bit;


		xConsole.debug("Strength.reconcileDrivers:->");
		xConsole.debug("bit1: " + strengthToString(strength1, bit1) +
				"          bit2: " + strengthToString(strength2, bit2));

		if (bit1 == Z) {
			xConsole.debug("bit1 is Z; the result is: " +
					strengthToString(strength2, bit2));
			return ((int)bit2 << 16) | ((int)strength2 & 0x000000ff);
		}
		if (bit2 == Z) {
			xConsole.debug("bit2 is Z; the result is: " +
					strengthToString(strength1, bit1));
			return ((int)bit1 << 16) | ((int)strength1 & 0x000000ff);
		}
		//so, along from here, there will be no Z value:

		byte aux = getStrengthForValue(strength1, bit1);
		if (aux == getStrengthForValue(strength2, bit2) && aux != 0xffff) {
			//equal, unambiguous strength: use truth table & be happy
			xConsole.debug("equal, unambiguous strength: use truth table & be happy");
			bit = truthTable[bit1][bit2];
			strength = getStrength(aux, aux); //make a clean unambiguous strength
		} else {
			//unequal strengths or ambiguous ones
			xConsole.debug("unequal strengths or ambiguous ones");
			if (!isAmbiguous(strength1, bit1)) {
				if (!isAmbiguous(strength2, bit2)) {
					//unambiguous strength & different values : the stronger rules
					xConsole.debug("unambiguous strength & different values : " +
					"the stronger rules");
					if (getStrengthForValue(strength1, bit1) >
					getStrengthForValue(strength2, bit2)) {
						bit = bit1;
						strength = strength1;
					} else {
						bit = bit2;
						strength = strength2;
					}
				} else {
					//1 is unambiguous, 2 is ambiguous:
					xConsole.debug("1 is unambiguous, 2 is ambiguous:");
					if (bit2 == X) {
						if (getStrengthForValue(strength1, bit1) >
						getStrengthForValue(strength2, 1-bit1)) {
							//the strength of the unambiguous bit is greater
							//than the other, so everything is ok:
							xConsole.debug("the strength of the unambiguous bit" +
							" is greater than the other, so everything is ok:");
							bit = bit1;
							strength = strength1;
						} else {
							//the value is still ambiguous
							xConsole.debug("the value is still ambiguous");
							bit = bit2;
							if (getStrengthForValue(strength1, bit1) >
							getStrengthForValue(strength2, bit1)) {
								//the strength interval will be increased:
								xConsole.debug("the strength interval will be increased:");
								//here bit1 can only be 0 or 1:
								if (bit1 == 0)
									strength = getStrength(getStrengthForValue(strength1, 0),
											getStrengthForValue(strength2, 1));
								else
									strength = getStrength(getStrengthForValue(strength2, 0),
											getStrengthForValue(strength1, 1));
							} else {  //signal 1 is enclosed by the second one
								xConsole.debug("signal 1 is enclosed by the second one");
								strength = strength2;
							}
						}
					} else {
						//bit2 is defined, but has a range of strengths
						xConsole.debug("bit2 is defined, but has a range of strengths");
						if (bit1 != bit2) {
							//an X will result here
							xConsole.debug("an X will result here");
							bit = X;
							if (bit1 == 0)
								strength = getStrength(strength1,
										(byte) Math.max(strength2 & 0x7,
												(strength2 >> 4) & 0x7));
							else
								strength = getStrength((byte) Math.max(strength2 & 0x7,
										(strength2 >> 4) & 0x7),
										strength1);
						} else {
							//bit1 == bit2, but strength2 is an interval: 
							//try to expand or shrink it according to strength1
							xConsole.debug("bit1 == bit2, but strength2 is an interval: \n" +
							"try to expand or shrink it according to strength1");
							bit = bit1;

							byte strengthInterval2 = (byte) (strength2 & 0x7);
							byte strengthInterval1 = (byte) ((strength2 >> 4) & 0x7);  //the interval is 1..2
							strength = getStrengthForValue(strength1, bit1);
							if (strength < strengthInterval2) {
								if (strength < strengthInterval1)strengthInterval1 = strength;
								strength = getStrength(strengthInterval1,
										strengthInterval2, bit);
							} else {//strength1 if greater than any strength
								//in the range of strength2
								xConsole.debug("strength1 if greater than any strength in" +
								" the range of strength2");
								strength = strength1; 
							}
						}
					}
				}//1 unambiguous, 2 ambiguous
			} else {
				//1 is ambiguous
				xConsole.debug("1 is ambiguous");
				if (!isAmbiguous(strength2, bit2)) {
					//1 ambiguous, 2 unambiguous
					xConsole.debug("1 ambiguous, 2 unambiguous");
					return reconcileDrivers(truthTable, strength2,
							strength1, bit2, bit1); //swap the values,
					//so it will match
					//the previous case
					//(1 ok, 2 ambiguous)
				} else {
					//1 & 2 are ambiguous
					xConsole.debug("1 & 2 are ambiguous");
					if (bit1 == X) {
						if (bit2 == X) {
							//both are x, so just modify the strength limits:
							xConsole.debug("both are x, so just modify the strength limits:");
							bit = X;
							strength = getStrength((byte) Math.max((strength1 >> 4) & 0x7,
									(strength2 >> 4) & 0x7),
									(byte) Math.max(strength1 & 0x7,
											strength2 & 0x7));
						} else {
							//bit1 == X, bit2 if defined but has a range of strengths:  (*)
							xConsole.debug("bit1 == X, bit2 if defined but has" +
							" a range of strengths:  (*)");
							if (((strength2 >>4) & 0x7) > Math.max(strength1 & 0x7,
									(strength1 >> 4) & 0x7)) {
								//the minimum strength of 2 is greater than 1, so keep it alone
								xConsole.debug("the minimum strength of 2 is greater " +
								"than 1, so keep it alone");
								bit = bit2;
								strength = strength2;
							} else {
								//the things get complicated: we have to
								//rebuild the strengths interval
								xConsole.debug("the things get complicated: we have" +
								" to rebuild the strengths interval");
								bit = X;
								if (bit2 == 0) {
									if ((strength2 & 0x7) > ((strength1 >> 4) & 0x7))
										strength = getStrength((byte) (strength2 & 0x7),
												(byte) (strength1 & 0x7));
									else strength = strength1;
								} else {
									if ((strength2 & 0x7) > (strength1 & 0x7))
										strength = getStrength((byte) ((strength1 >> 4) & 0x7),
												(byte) (strength2 & 0x7));
									else strength = strength1;
								}
							}
						}
					} else {
						//bit1 != X
						xConsole.debug("bit1 != X");
						if (bit2 == X) {
							//bit1 != X and has a range of strengths,
							//bit2 == X : symmetrical with (*) (kill&yanke&replace'd)
							xConsole.debug("bit1 != X and has a range of strengths," +
									" bit2 == X : symmetrical with (*)" +
							"(kill&yanke&replace'd)");
							if (((strength1 >> 4) & 0x7) > Math.max(strength2 & 0x7,
									(strength2 >> 4) & 0x7)) {
								//the minimum strength of 1 is greater than 2's,
								//so keep it alone
								xConsole.debug("the minimum strength of 1 is greater than" +
								" 2's, so keep it alone");
								bit = bit1;
								strength = strength1;
							} else {
								//the things get complicated: we have to
								//rebuild the strengths interval
								xConsole.debug("the things get complicated: we have" +
								" to rebuild the strengths interval");
								bit = X;
								if (bit1 == 0) {
									if ((strength1 & 0x7) > ((strength2 >> 4) & 0x7))
										strength = getStrength((byte) (strength1 & 0x7),
												(byte) (strength2 & 0x7));
									else strength = strength2;
								} else {
									if ((strength1 & 0x7) > (strength2 & 0x7))
										strength = getStrength((byte) ((strength2 >> 4) & 0x7),
												(byte) (strength1 & 0x7));
									else strength = strength2;
								}
							}
						} else {
							//bit1 != X, bit2 != X : both have strength intervals:
							xConsole.debug("bit1 != X, bit2 != X : both have strength" +
							" intervals:");
							if (bit1 == 0) {
								if (bit2 == 0) {
									//bit1 == bit2 == 0
									xConsole.debug("bit1 == bit2 == 0");
									bit = 0;
									strength = getStrength((byte) Math.max((strength1 >>4) & 0x7,
											(strength2 >> 4) & 0x7),
											(byte) Math.max(strength1 & 0x7,
													strength2 & 0x7), bit);
								} else {
									//bit1 == 0, bit2 ==1
									xConsole.debug("bit1 == 0, bit2 ==1");
									bit = X;
									strength = getStrength((byte) (strength1 & 0x7),
											(byte) (strength2 & 0x7), bit);
								}
							} else {
								//bit1 != 0
								xConsole.debug("bit1 != 0");
								if (bit2 == 0) {
									//bit1 == 1, bit2 == 0
									xConsole.debug("bit1 == 1, bit2 == 0");
									bit = X;
									strength = getStrength((byte) (strength2 & 0x7),
											(byte) (strength1 & 0x7), bit);
								} else {
									//bit1 ==1, bit2 == 1
									xConsole.debug("bit1 ==1, bit2 == 1");
									bit = 1;
									strength = getStrength((byte) Math.max((strength1 >>4) & 0x7,
											(strength2 >> 4) & 0x7),
											(byte) Math.max(strength1 & 0x7,
													strength2 & 0x7), bit);
								}
							}
						}
					}
				}
			}
		}
		xConsole.debug("And, finally, the result is: " +
				strengthToString(strength, bit));
		return (bit << 16) | ((int)strength & 0x000000ff);
	} //and here finishes the biggest routine in the whole project (till now :))

}






